package com.changgou.seckill.task;

import com.changgou.seckill.dao.SeckillGoodsMapper;
import com.changgou.seckill.pojo.SeckillGoods;
import com.changgou.util.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * 定时器, 定时执行, 将mysql中秒杀商品数据读取出来, 写入到redis中缓存上
 */
@Component
public class PushSeckillGoods {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 定时器方法, 每20秒执行一次, 将mysql中秒杀商品数据读取出来, 写入到redis中缓存上
     */
    @Scheduled(cron = "0/20 * * * * ?")
    public void pushSeckillGoodsToRedis() throws Exception {
        //1）获取秒杀时间段菜单信息, 一天分为12场, 两个小时一个秒杀场次, 这里默认从当前时间算, 加载5场
        List<Date> dateMenus = DateUtil.getDateMenus();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        if (dateMenus != null) {
            //2）遍历每一个时间段，添加该时间段下秒杀商品
            for (Date dateMenu : dateMenus) {
                //拿这个时间菜单作为保存到redis中的key使用
                String redisKey = DateUtil.date2Str(dateMenu);

                // 2.1）将当前时间段转换为String，作为redis中的key
                // 2.2）查询商品信息（状态为1，库存大于0，秒杀商品开始时间大于当前时间段，
                // 秒杀商品结束时间小于当前时间段，当前商品的id不在redis中）
                //创建查询对象
                Example example = new Example(SeckillGoods.class);
                //创建sql语句中where条件对象
                Example.Criteria criteria = example.createCriteria();
                //秒杀商品状态为1, 审核通过的
                criteria.andEqualTo("status", "1");
                //商品剩余库存量大于等于1
                criteria.andGreaterThanOrEqualTo("stockCount", 1);

                String startTime = sdf.format(dateMenu);
                Date endTime = DateUtil.addDateHour(sdf.parse(startTime), 2);
                //数据库中秒杀起始时间大于等于秒杀菜单的起始时间
                criteria.andGreaterThanOrEqualTo("startTime", sdf.parse(startTime));
                //数据库中秒杀结束时间小于秒杀菜单的结束时间
                criteria.andLessThan("endTime", endTime);

                //先到redis中查询redis中已经存在的秒杀商品数据
                Set<String> seckillGoodsIds = redisTemplate.boundHashOps(redisKey).keys();
                //将redis中已经存在的秒杀商品数据, 放入mysql查询条件中, 不查询出redis中已经有的数据
                if (seckillGoodsIds != null && seckillGoodsIds.size() > 0) {
                    for (String seckillGoodsId : seckillGoodsIds) {
                        criteria.andNotEqualTo("id", seckillGoodsId);
                    }
                }

                List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectByExample(example);

                //3）添加redis
                if (seckillGoodsList != null && seckillGoodsList.size() > 0) {
                    for (SeckillGoods seckillGoods : seckillGoodsList) {
                        /**
                         * redis中保存秒杀商品数据格式例如:
                         * key秒杀时间菜单                value可以保存Hash类型的
                         * 2020042210                   秒杀商品id作为小key,    秒杀商品对象
                         *                              1           seckillGoods
                         *                              2           seckillGoods
                         * 2020042212
                         */
                        redisTemplate.boundHashOps(redisKey).put(String.valueOf(seckillGoods.getId()), seckillGoods);

                        /**
                         * 单独保存一份秒杀商品库存剩余量
                         * 原因: 秒杀的时候异步扣减库存使用. 需要保证扣减的时候的原子操作.
                         * 如果不能够保证库存数据的原子性, 那么在高并发的时候, 会发生超卖.
                         * springDateRedis中对于bound....这一套api中底层源码有bug的原因, 无法进行原子性操作.
                         * 需要使用ops....这一套api进行redis中的原子性操作.
                         */
                        redisTemplate.opsForValue().set(String.valueOf(seckillGoods.getId()), seckillGoods.getStockCount());
                    }
                }
            }
        }


    }
}
